package coordinator

import (
	"adai.design/homemaster/container"
	"adai.design/homemaster/container/characteristic"
	"adai.design/homemaster/container/characteristic/to"
	"adai.design/homemaster/container/service"
	"adai.design/homemaster/log"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"io/ioutil"
	"os"
	"runtime"
	"time"
)

type Coordinator struct {
	// 网络信息
	NetAddr  string `json:"net_addr"`
	Mac      string `json:"mac"`
	PANId    string `json:"pan_id"`
	ExtPANId string `json:"ext_pan_id"`
	Channel  uint8  `json:"channel"`

	permitJoin int // 是否开启入网许可(0:关闭  >0: 开启时间)

	// 设备信息
	SdkVersion uint16 `json:"sdk_version"`
	AppVersion uint16 `json:"app_version"`

	service *CoordinatorService
}

func (c *Coordinator) configFile() string {
	if runtime.GOARCH != "mipsle" {
		path := os.Getenv("GOPATH") + "/src/adai.design/homemaster/build/tmp/"
		file := path + "coordinator.cfg"
		return file
	}
	return "/etc/homemaster/coordinator.cfg"
}

func (c *Coordinator) save() error {
	data, err := json.Marshal(c)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(c.configFile(), data, 0666)
}

func (c *Coordinator) load() error {
	data, err := ioutil.ReadFile(c.configFile())
	if err != nil {
		log.Error("%s", err)
		return err
	}
	err = json.Unmarshal(data, c)
	if err == nil {
		return nil
	}
	return err
}

func (c *Coordinator) getVersion() error {
	msg := &Message{MsgT: SerialLinkMsgTypeGetVersion, Data: nil}
	sdata, err := msg.Encode()
	if err != nil {
		return err
	}
	addTask(sdata)
	addObserver(SerialLinkMsgTypeVersionList, c.observerGetVersionResponse)
	return nil
}

func (c *Coordinator) setPermitJoin(interval int) error {
	c.permitJoin = interval
	go func() {
		for {
			<-time.After(time.Second)
			c.permitJoin -= 1
			c.service.PermitJoin.SetValue(c.permitJoin)
			if c.permitJoin <= 0 {
				return
			}
		}
	}()

	msg := &Message{MsgT: SerialLinkPermitJoinRequest, Data: []byte{0xff, 0xfc, uint8(interval), 0x00}}
	sdata, err := msg.Encode()
	if err != nil {
		return err
	}
	addTask(sdata)
	addObserver(SerialLinkMsgTypeVersionList, c.observerGetVersionResponse)
	return nil
}

func (c *Coordinator) observerGetVersionResponse(m *Message) error {
	if m == nil || len(m.Data) < 4 {
		log.Printf("mark msg(%x) data(%x)\n", m.MsgT, m.Data)
		return nil
	}

	c.SdkVersion = binary.BigEndian.Uint16(m.Data[0:])
	c.AppVersion = binary.BigEndian.Uint16(m.Data[2:])

	log.Printf("coordinator sdk(%d)\n", c.SdkVersion)
	log.Printf("coordinator application(%d)\n", c.AppVersion)
	return nil
}

func (c *Coordinator) getNetworkState() error {
	msg := &Message{MsgT: SerialLinkMsgTypeNetworkStateReq, Data: nil}
	sdata, err := msg.Encode()
	if err != nil {
		return err
	}
	addTask(sdata)
	addObserver(SerialLinkMsgTypeNetworkStateRsp, c.observerNetworkStateResponse)
	return nil
}

func (c *Coordinator) observerNetworkStateResponse(m *Message) error {
	if m == nil || len(m.Data) < 21 {
		return nil
	}

	netAddr := fmt.Sprintf("%x", m.Data[0:2])
	if netAddr != c.NetAddr {
		c.NetAddr = netAddr
		c.service.NetAddr.SetValue(netAddr)
	}

	macAddr := fmt.Sprintf("%x", m.Data[2:2+8])
	if macAddr != c.Mac {
		c.Mac = macAddr
		c.service.MacAddr.SetValue(macAddr)
	}

	panId := fmt.Sprintf("%x", m.Data[10:12])
	if panId != c.PANId {
		c.PANId = panId
		c.service.PanId.SetValue(panId)
	}

	extPanId := fmt.Sprintf("%x", m.Data[12:12+8])
	if extPanId != c.ExtPANId {
		c.ExtPANId = extPanId
		c.service.ExtPanId.SetValue(extPanId)
	}

	channel := m.Data[20]
	if channel != c.Channel {
		c.Channel = channel
		c.service.Chanel.SetValue(int(channel))
	}

	c.save()
	return nil
}

// 协调器服务
const CoordinatorSevId = 20

type CoordinatorService struct {
	*service.Service

	NetAddr    *characteristic.String
	MacAddr    *characteristic.String
	PanId      *characteristic.String
	ExtPanId   *characteristic.String
	Chanel     *characteristic.Int
	PermitJoin *characteristic.Int
}

func newCoordinatorService(sevId int, c *Coordinator) *CoordinatorService {
	coordinator := &CoordinatorService{
		Service: service.New(service.TypeZigbeeCoordinator),
	}

	coordinator.MacAddr = characteristic.NewString(characteristic.TypeCoordinatorMacAddr)
	coordinator.MacAddr.Id = 1
	coordinator.MacAddr.Value = c.Mac
	coordinator.Service.AddCharacteristic(coordinator.MacAddr.Characteristic)

	coordinator.NetAddr = characteristic.NewString(characteristic.TypeCoordinatorNetAddr)
	coordinator.NetAddr.Id = 2
	coordinator.NetAddr.Value = c.NetAddr
	coordinator.Service.AddCharacteristic(coordinator.NetAddr.Characteristic)

	coordinator.PanId = characteristic.NewString(characteristic.TypeCoordinatorPanId)
	coordinator.PanId.Id = 3
	coordinator.PanId.Value = c.PANId
	coordinator.Service.AddCharacteristic(coordinator.PanId.Characteristic)

	coordinator.ExtPanId = characteristic.NewString(characteristic.TypeCoordinatorExtPanId)
	coordinator.ExtPanId.Id = 4
	coordinator.ExtPanId.Value = c.ExtPANId
	coordinator.Service.AddCharacteristic(coordinator.ExtPanId.Characteristic)

	coordinator.Chanel = characteristic.NewInt(characteristic.TypeCoordinatorChannel)
	coordinator.Chanel.Format = characteristic.FormatUInt8
	coordinator.Chanel.Id = 5
	coordinator.Chanel.Value = c.Channel
	coordinator.Service.AddCharacteristic(coordinator.Chanel.Characteristic)

	coordinator.PermitJoin = characteristic.NewInt(characteristic.TypeCoordinatorPermitJoin)
	coordinator.Chanel.Format = characteristic.FormatUInt8
	coordinator.PermitJoin.Id = 6
	coordinator.PermitJoin.Value = c.permitJoin
	coordinator.Service.AddCharacteristic(coordinator.PermitJoin.Characteristic)

	coordinator.PermitJoin.OnValueUpdateFromConn(func(conn *websocket.Conn, char *characteristic.Characteristic, newValue, oldValue interface{}) {
		interval := to.Int64(newValue)
		if interval > 0 {
			c.setPermitJoin(int(interval))
		}
	})
	return coordinator
}

var coordinator = &Coordinator{}

func init() {
	coordinator.load()
	coordinator.service = newCoordinatorService(CoordinatorSevId, coordinator)
	container.AddService(coordinator.service.Service, CoordinatorSevId)
}

func StartCoordinator() {
	serialOpen()
	coordinator.getVersion()
	coordinator.getNetworkState()
	//coordinator.setPermitJoin(100)
	zigbeeManagerInit()
}

func StopCoordinator() {
	serialClose()
}
